// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt
// Project developers.  See the top-level LICENSE file for dates and other
// details.  No copyright assignment is required to contribute to VisIt.

// ************************************************************************* //
//                             ZoomInteractor.C                              //
// ************************************************************************* //

#include <vtkActor2D.h>
#include <vtkCellArray.h>
#include <vtkPoints.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper2D.h>
#include <vtkProperty2D.h>
#include <vtkRenderer.h>
#include <vtkRenderWindowInteractor.h>

#include <VisWindow.h>
#include <VisWindowInteractorProxy.h>
#include <ZoomInteractor.h>


// ****************************************************************************
//  Method:  ZoomInteractor constructor
//
//  Programmer: Hank Childs
//  Creation:   May 22, 2000
//
//  Modifications:
//
//    Hank Childs, Wed Jul 26 11:42:39 PDT 2000
//    Removed call to SetCanvasViewport, since that is now done every time
//    rubber band mode is started so re-sizes can be picked up.
//
//    Kathleen Bonnell, Thu Nov 15 12:24:45 PST 2001 
//    Change ids from int to vtkIdType to match VTK 4.0 API. 
//
//    Akira Haddox, Thu Jul  3 14:15:48 PDT 2003
//    Added initialization of shift and controlKeyDown.
//    
//    Kathleen Bonnell, Wed Aug 18 10:10:35 PDT 2004 
//    Set shouldClampSquare and shouldDrawGuides based on InteractorAtts.
//
//    Brad Whitlock, Fri Mar 10 17:43:07 PST 2006
//    Added APPLE-specific code where we create polydata that can contain
//    4 points instead of 2. This lets us draw the lines at the same time
//    into an overlay window.
//
//    Brad Whitlock, Fri Oct 14 16:27:15 PDT 2011
//    Create mapper via proxy.
//
//    Kathleen Biagas, Mon Jun 11 17:15:39 MST 2012 
//    Have windows follow the APPLE path. 
//
//    Eric Brugger, Wed Oct  2 16:54:48 PDT 2024
//    I modified the class to use the APPLE path in all cases.
//
// ****************************************************************************

ZoomInteractor::ZoomInteractor(VisWindowInteractorProxy &vw) 
    : VisitInteractor(vw)
{
    rubberBandMode  = false;
    shiftKeyDown = controlKeyDown = false;

    VisWindow *win = vw;
    shouldClampSquare = win->GetInteractorAtts()->GetClampSquare();
    shouldDrawGuides = win->GetInteractorAtts()->GetShowGuidelines();

    //
    // Create the poly data that will map the rubber band onto the screen.
    //
    rubberBand       = vtkPolyData::New();

    vtkPoints *pts = vtkPoints::New();
    pts->SetNumberOfPoints(4);
    rubberBand->SetPoints(pts);
    pts->Delete();

    vtkCellArray *lines  = vtkCellArray::New();
    vtkIdType  ids[] = { 0, 1, 2, 3, 0};
    lines->InsertNextCell(5, ids);
    rubberBand->SetLines(lines);
    lines->Delete();

    rubberBandMapper = proxy.CreateRubberbandMapper();
    rubberBandMapper->SetInputData(rubberBand);

    rubberBandActor  = vtkActor2D::New();
    rubberBandActor->SetMapper(rubberBandMapper);
    rubberBandActor->GetProperty()->SetColor(0., 0., 0.);
}


// ****************************************************************************
//  Method: ZoomInteractor destructor
//
//  Programmer: Hank Childs
//  Creation:   May 24, 2000
//
// ****************************************************************************

ZoomInteractor::~ZoomInteractor()
{
    rubberBand->Delete();
    rubberBandMapper->Delete();
    rubberBandActor->Delete();
}


// ****************************************************************************
//  Method: ZoomInteractor::SetCanvasViewport
//
//  Purpose:
//      Sets where the canvas is in display coordinates so that rubber bands
//      outside the canvas can be forced inside the canvas.
//
//  Programmer: Hank Childs
//  Creation:   May 24, 2000
//
// ****************************************************************************

void
ZoomInteractor::SetCanvasViewport(void)
{
    vtkRenderer *ren = proxy.GetCanvas();

    canvasDeviceMinX = 0.;
    canvasDeviceMinY = 0.;
    ren->NormalizedViewportToViewport(canvasDeviceMinX, canvasDeviceMinY);
    ren->ViewportToNormalizedDisplay(canvasDeviceMinX, canvasDeviceMinY);
    ren->NormalizedDisplayToDisplay(canvasDeviceMinX, canvasDeviceMinY);

    canvasDeviceMaxX = 1.;
    canvasDeviceMaxY = 1.;
    ren->NormalizedViewportToViewport(canvasDeviceMaxX, canvasDeviceMaxY);
    ren->ViewportToNormalizedDisplay(canvasDeviceMaxX, canvasDeviceMaxY);
    ren->NormalizedDisplayToDisplay(canvasDeviceMaxX, canvasDeviceMaxY);
}


// ****************************************************************************
//  Method: ZoomInteractor::StartRubberBand
//
//  Purpose:
//      Tells the interactor to draw a rubber band and update it whenever the
//      mouse moves.
//
//  Arguments:
//      x       The x-coordinate of the pointer in display coordinates when
//              the button was pressed.
//      y       The y-coordinate of the pointer in display coordinates when
//              the button was pressed.
//
//  Programmer: Hank Childs
//  Creation:   May 22, 2000
//
//  Modifications:
//
//    Hank Childs, Wed Jul  5 11:32:18 PDT 2000
//    Removed call to StartTimer since pointer motions are now picked up
//    by the interactor and the timer is incompatible (and redundant) with it.
//
//    Hank Childs, Wed Jul 26 11:42:39 PDT 2000
//    Added call to SetCanvasViewport.  Done here to pick up resizes between
//    rubber band modes (as compared to constructor).
//
//    Hank Childs, Tue Aug  1 15:37:11 PDT 2000
//    Moved SetCanvasViewport to be before ForceCoordsToViewport.
//
//    Akira Haddox, Thu Jul  3 14:17:48 PDT 2003
//    Set shift and controlKeyDown based on interactor information.
//
//    Kathleen Bonnell, Wed Aug 18 10:10:35 PDT 2004 
//    Set shouldClampSquare and shouldDrawGuides based on InteractorAtts.
//
//    Brad Whitlock, Mon Mar 13 11:08:22 PDT 2006
//    I added code to set the actor's color to the current foreground
//    color so it uses the right color on MacOS X. Other platforms use the
//    XOR drawing mode so the color will vary there.
//
// ****************************************************************************

void
ZoomInteractor::StartRubberBand(int x, int y)
{
    rubberBandMode = true;

    shiftKeyDown = Interactor->GetShiftKey();
    controlKeyDown = Interactor->GetControlKey();

    VisWindow *win = proxy;
    shouldClampSquare = win->GetInteractorAtts()->GetClampSquare();
    shouldDrawGuides = win->GetInteractorAtts()->GetShowGuidelines();

    // Set the actor's color.
    double fg[3];
    proxy.GetForegroundColor(fg);
    rubberBandActor->GetProperty()->SetColor(fg[0], fg[1], fg[2]);

    //
    // Add the rubber band actors to the background.  We do this since the
    // background is in the same display coordinates that the rubber band will
    // be.  From an appearance standpoint it should be in the canvas, which
    // the routine ForceCoordsToViewport ensures.
    //
    vtkRenderer *ren = proxy.GetBackground();
    ren->AddActor2D(rubberBandActor);

    //
    // The anchor of the rubber band will be where the button press was.
    //
    anchorX = x;
    anchorY = y;

    //
    // Determine what to clamp the rubber band to.
    //
    SetCanvasViewport();

    //
    // If the user has clicked outside the viewport, force the back inside.
    //
    ForceCoordsToViewport(anchorX, anchorY);

    //
    // Must update bookkeeping so that OnMouseMove works correctly.
    //
    lastX   = anchorX;
    lastY   = anchorY;

    rubberBandDrawn = false;
}


// ****************************************************************************
//  Method: ZoomInteractor::OnMouseMove
//
//  Purpose:
//      Receives a mouse movement and updates the rubber band.
//
//  Arguments:
//      ctrl          non-zero if the ctrl button was pushed.
//      shift         non-zero if the shift button was pushed.
//      x             The x-coordinate on the mouse pointer in display coords.
//      y             The y-coordinate on the mouse pointer in display coords.
//     
//  Programmer: Hank Childs
//  Creation:   May 22, 2000
//
//  Modifications:
//    Kathleen Bonnell, Fri Dec 13 16:41:12 PST 2002 
//    Removed arguments to comply with vtks' new interactor interface.
//    Values are now accessed via the RenderWindowInteractor.
//
//    Akira Haddox, Thu Jul  3 14:17:48 PDT 2003
//    Force a square rubber band when shift key is down.
//
//    Kathleen Bonnell, Wed Aug 18 10:10:35 PDT 2004 
//    Force a square rubber band when the global flag is turned on, or
//    the shift key is down. 
//
// ****************************************************************************

void
ZoomInteractor::OnMouseMove()
{
    int x, y;
    if (rubberBandMode)
    {
        Interactor->GetEventPosition(x, y);
        ForceCoordsToViewport(x, y);

        //
        // If the shift key is down, lock the coordinates so that we form
        // a square.
        //
        if (shouldClampSquare || shiftKeyDown)
        {
            int deltaX = x - anchorX;
            int deltaY = y - anchorY;
            
            int absDeltaX = abs(deltaX);
            int absDeltaY = abs(deltaY);
            
            if (absDeltaX > absDeltaY)
            {
                // x stays the same, adjust y
                if (deltaY < 0)
                    y = anchorY - absDeltaX;
                else
                    y = anchorY + absDeltaX;
            }
            else
            {
                // y stays the same, adjust x
                if (deltaX < 0)
                    x = anchorX - absDeltaY;
                else
                    x = anchorX + absDeltaY;
            }

            // We now have a square, but it might be outside our bounds.
            // We force the coordinates again, since we take the larger
            // dimension to expand the square on, and we might have
            // expanded outside the box.
            // If these coordinates change, then we need to make it a box
            // again.
            //
            int oldX = x;
            int oldY = y;
            ForceCoordsToViewport(x, y);
            oldX = abs(x-oldX);
            oldY = abs(y-oldY);

            // if x was forced, adjust y
            if (oldX)
            {
                if (deltaY < 0)
                    y += oldX;
                else
                    y -= oldX;
            }

            // if y was forced, adjust x
            if (oldY)
            {
                if (deltaX < 0)
                    x += oldY;
                else
                    x -= oldY;
            }
        }

        UpdateRubberBand(anchorX, anchorY, lastX, lastY, x, y);
        lastX = x;
        lastY = y;
    }
    else
    {
        //
        // We are not in rubber band mode, so we don't want to override
        // the interactor.  Call the base function.
        //
        VisitInteractor::OnMouseMove();
    }
}


// ****************************************************************************
//  Method: ZoomInteractor::EndRubberBand
//
//  Purpose:
//      Tells the interactor to remove the rubber band and no longer update it 
//      whenever the mouse moves.
//
//  Programmer: Hank Childs
//  Creation:   May 22, 2000
//
//  Modifications:
//
//    Hank Childs, Mon Jun 26 17:09:25 PDT 2000
//    Removed unused arguments for the location of the mouse pointer.
//
//    Hank Childs, Wed Jul  5 11:32:18 PDT 2000
//    Removed call to StartTimer since pointer motions are now picked up
//    by the interactor and the timer is incompatible (and redundant) with it.
//
// ****************************************************************************

void
ZoomInteractor::EndRubberBand()
{
    rubberBandMode = false;

    //
    // Remove the rubber band actors from the renderer.
    // We should use the background as the renderer because our rubber band
    // is in display coordinates.
    //
    vtkRenderer *ren = proxy.GetBackground();
    ren->RemoveActor2D(rubberBandActor);
}


// ****************************************************************************
//  Method: ZoomInteractor::ForceCoordsToViewport
//
//  Purpose:
//      Takes coordinates and makes sure they are in the viewport.  If not,
//      it forces them inside the viewport.
//
//  Arguments:
//      x      X coordinate in display coordinates.
//      y      Y coordinate in display coordinates.
//
//  Note:    This routine assumes the canvasDevice[Min|Max][X|Y] has
//           already been set.
//
//  Programmer: Hank Childs
//  Creation:   May 22, 2000
//
//  Modifications:
//
//    Hank Childs, Tue Sep 18 12:02:29 PDT 2001
//    Cast return value of ceil/floor to get rid of compiler warnings.
//
//    Kathleen Bonnell, Wed Jul  2 09:12:52 PDT 2003  
//    Switch ceil to floor and vice-versa, so that rubber ban can extend
//    all the way to the xmin, xmax, ymin, ymax. 
//
// ****************************************************************************

void
ZoomInteractor::ForceCoordsToViewport(int &x, int &y)
{
    if (x < canvasDeviceMinX)
    {
        x = (int)floor(canvasDeviceMinX);
    }
    if (x > canvasDeviceMaxX)
    {
        x = (int)ceil(canvasDeviceMaxX);
    }
    if (y < canvasDeviceMinY)
    {
        y = (int)floor(canvasDeviceMinY);
    }
    if (y > canvasDeviceMaxY)
    {
        y = (int)ceil(canvasDeviceMaxY);
    }
}

// ****************************************************************************
//  Method: ZoomInteractor::UpdateRubberBand
//
//  Purpose:
//      The real rubber band function.  This takes six arguments specifying
//      the anchor of the rubber band, what the last opposite corner was,
//      and what the new opposite corner is.  It implements the rubber band
//      through an XOR found in any text book.  To prevent blinking effects,
//      if extends the old rubber band lines instead of wiping them out and
//      redrawing.
//
//  Arguments:
//      aX      The x-coordinate of the anchor in display coordinates.
//      aY      The y-coordinate of the anchor in display coordinates.
//      lX      The x-coordinate of the last corner in display coordinates.
//      lY      The y-coordinate of the last corner in display coordinates.
//      nX      The x-coordinate of the new corner in display coordinates.
//      nY      The y-coordinate of the new corner in display coordinates.
//
//  Programmer: Hank Childs
//  Creation:   May 22, 2000
//
//  Modifications:
//    Brad Whitlock, Fri Mar 10 17:45:22 PST 2006
//    I added code for APPLE that lets us draw all line segments at the same
//    time. The mapper that we're using in this case does not XOR so it's ok.
//   
//    Kathleen Biagas, Mon Jun 11 17:15:39 MST 2012 
//    Have windows follow the APPLE path. 
//
//    Eric Brugger, Wed Oct  2 16:54:48 PDT 2024
//    I modified the class to use the APPLE path in all cases.
//
// ****************************************************************************

void
ZoomInteractor::UpdateRubberBand(int aX, int aY, int lX, int lY,int nX,int nY)
{
    if (nX == lX && nY == lY)
    {
        //
        // No update necessary.
        //
        return;
    }

    // This code assumes that the lines will be drawn at the same time
    // in an overlay window that the rubber-band mapper will create.
    vtkViewport *ren = proxy.GetBackground();
    vtkPoints *pts = rubberBand->GetPoints();
    pts->SetPoint(0, (double) aX, (double) aY, 0.);   
    pts->SetPoint(1, (double) nX, (double) aY, 0.);   
    pts->SetPoint(2, (double) nX, (double) nY, 0.);   
    pts->SetPoint(3, (double) aX, (double) nY, 0.);
    rubberBandMapper->RenderOverlay(ren, rubberBandActor);
}

// ****************************************************************************
//  Method: ZoomInteractor::SufficientDistanceMoved
//
//  Purpose:
//      Determines if the current zoom coordinates are sufficiently far apart
//      to justify performing the zoom, or if it should be cancelled instead
//
//  Programmer: Marc Durant
//  Creation:   December 19, 2011
//
// ****************************************************************************

bool
ZoomInteractor::SufficientDistanceMoved() {

    float distX = (lastX - anchorX) * (lastX - anchorX);
    if (distX <= 16) {
        return false;
    }

    float distY = (lastY - anchorY) * (lastY - anchorY);
    if (distY <= 16) {
        return false;
    }

    return true;
}
